#include <atomic>

#include"log.h"
#include"fiber.h"
#include"config.h"
#include"macro.h"
#include"scheduler.h"



namespace qtch{

static qtch::Logger::ptr logger = QTCH_LOG_NAME("system");

static std::atomic<uint64_t> s_fiber_id {0};
static std::atomic<uint64_t> s_fiber_count {0};

static thread_local Fiber::ptr t_fiber = nullptr;

static ConfigVar<uint32_t>::ptr g_fiber_stack_size = 
    Config::LookUp<uint32_t>("fiber.stack_size",128 * 1024,"fiber stack size");

class MallocStackAllocator{
public:
    static void * Alloc(size_t size){
        return malloc(size);
    }
    static void Dealloc(void* vp,size_t size){
        return free(vp);
    }
};

using StackAllocator = MallocStackAllocator;


Fiber::Fiber(std::function<void()> cb, size_t stackSize)
    :m_id(++s_fiber_id)
    ,m_cb(cb){
    ++s_fiber_count;
    m_stackSize = stackSize ? stackSize : g_fiber_stack_size->getValue();
    m_stack = StackAllocator::Alloc(m_stackSize);
    if(getcontext(&m_ctx)){
        QTCH_ASSERT2(false, "getcontext");
    }

    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stackSize;
    m_ctx.uc_stack.ss_flags = 0;
    m_ctx.uc_link = nullptr;
    
    makecontext(&m_ctx,&Fiber::MainFun,0);
    m_state = INIT;
    //QTCH_LOG_DEBUG(logger) << "create Fiber::Fiber id = " << m_id;
}
Fiber::Fiber()
    :m_id(++s_fiber_id){
    ++s_fiber_count;
    m_state = EXEC;
    if(getcontext(&m_ctx)) {
        QTCH_ASSERT2(false, "getcontext");
    }
    // QTCH_LOG_DEBUG(logger) << "Fiber::Fiber main";
}

Fiber::~Fiber(){
    --s_fiber_count;
    if(m_stack){
        QTCH_ASSERT(m_state == TERM
            || m_state == EXCEPT
            || m_state == INIT);
        StackAllocator::Dealloc(m_stack,m_stackSize);
    }


    // QTCH_LOG_DEBUG(logger) << "Fiber::Fiber id = " <<m_id
    //                     << " total fiber count = " << s_fiber_count;
}


void Fiber::swapIn(){
    SetThis(this->shared_from_this());
    QTCH_ASSERT(m_state != EXEC);
    m_state = EXEC;
    Fiber::ptr main_fiber = Scheduler::GetMainFiber();
    Fiber * auto_ptr = main_fiber.get();
    main_fiber.reset();
    if(swapcontext(&(auto_ptr->m_ctx),&m_ctx)){
        QTCH_ASSERT2(false,"swapcontext");
    }
}

void Fiber::swapOut(){
    SetThis(Scheduler::GetMainFiber());
    Fiber::ptr main_fiber = Scheduler::GetMainFiber();
    Fiber * auto_ptr = main_fiber.get();
    main_fiber.reset();
    if(swapcontext(&m_ctx,&(auto_ptr->m_ctx))){
        QTCH_ASSERT2(false,"swapcontext");
    }
}

void Fiber::reset(std::function<void()> cb){
    QTCH_ASSERT(m_stack);
    QTCH_ASSERT(m_state == TERM
            || m_state == EXCEPT
            || m_state == INIT);
    m_cb = cb;
    if(getcontext(&m_ctx)){
        QTCH_ASSERT2(false, "getcontext");
    }
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stackSize;
    m_ctx.uc_stack.ss_flags = 0;
    m_ctx.uc_link = nullptr;
    makecontext(&m_ctx,&Fiber::MainFun,0);
    m_state = INIT;
}

void Fiber::yieldToHold(){
    Fiber::ptr fiber = GetThis();
    QTCH_ASSERT2(fiber->m_state == EXEC ,"yieldToHold");
    fiber->m_state = HOLD;
    Fiber * temp_f =fiber.get();
    fiber.reset();
    temp_f->swapOut();
}

void Fiber::yieldToReady(){
    Fiber::ptr fiber = GetThis();
    QTCH_ASSERT2(fiber->m_state == EXEC ,"yieldToReady");
    fiber->m_state = READY;
    Fiber * temp_f =fiber.get();
    fiber.reset();
    temp_f->swapOut();
}


void Fiber::MainFun(){
    Fiber::ptr fiber = GetThis();
    try{
        fiber->m_cb();
        fiber->m_cb = nullptr;
        fiber->m_state = TERM;

    }catch(std::exception& ex){
        fiber->m_state = EXCEPT;
        QTCH_LOG_ERROR(logger) << "fiber exception: " << ex.what()
                    << "fiber id = " << fiber->getId()
                    << std::endl
                    << qtch::BacktraceToString();
    }
    catch(...){
        fiber->m_state = EXCEPT;
        QTCH_LOG_ERROR(logger) << "fiber Exception "
                    << "fiber id = " << fiber->getId()
                    << std::endl
                    << qtch::BacktraceToString();
    }
    auto raw_ptr = fiber.get();
    fiber.reset();
    raw_ptr->swapOut();
    
    QTCH_ASSERT2(false,"nerver reach fiber_id = " + std::to_string(raw_ptr->getId()));

}

void Fiber::SetThis(Fiber::ptr fiber){
    t_fiber = fiber;
}

Fiber::ptr Fiber::GetThis(){
    if(t_fiber){
        return t_fiber;
    }
    Fiber::ptr main_fiber(new Fiber());
    t_fiber = main_fiber;
    return t_fiber;
}


uint64_t Fiber::getFibersCount(){
    return s_fiber_count.load();
}

uint64_t Fiber::getFiberId(){
    if(t_fiber){
        return t_fiber->getId();
    }
    return 0;
}

}